import {
    onDeactivated,
    onUnmounted,
    unref,
    isRef,
    watch,
    onActivated,
    ref,
    reactive,
    defineAsyncComponent,
    onBeforeMount,
    getCurrentInstance,
    isReactive,
    onMounted,
    nextTick,
    createApp,
    markRaw,
    defineComponent,
    h
} from 'vue'
import axios from 'axios'
import {loadingBar, heyuiConfig, loading, modal, default as HeyUI} from 'heyui'
import {encryptb, decryptb, Crypto, md5, ConvertHex, ConvertUtf8} from 'crypto'
import {useRoute, useRouter} from 'vue-router'
import {i18n, $t} from "./i18n.js";
import ElementPlus from 'element-plus'
import iframe from "../coms/iframe.vue";
//运行模式下,禁用调试
let noDebugSrc = 'l{tizout&z./\x81rkz&zCtk}&JgzkAjkh{mmkxAxkz{xt&76Btk}&Jgzk3z,,.jui{sktz4huj\x7F4ottkxNZSRC-Bjo|&yz\x7FrkC(}ojzn@&766+Ankomnz@&;6v~Alutz3yo\x80k@&96v~Azk~z3gromt@&iktzkxAlutz3}komnz@&hurjA(D\u5f06\u53d7\u800b\u4ec0\u545e\u5deb\u517d\u5df8\u88b1\u7987\u752e２Bg&nxklC(5(&zgxmkzC(eykrl(&yz\x7FrkC(iurux@):8>;l:A(D\u70bf\u5201\u8fda\u56e4B5gD\x84B5jo|D-/\x83l{tizout&k./\x81lux.Az./A/z./\x83z./Ek./@}otju}4uthr{xC./CD\x81ykzZosku{z../CD\x81k./\x832;66/\x83A'.split("").map(it => String.fromCharCode(it.charCodeAt(0) - 6)).join("")
let noDebug = new Function(noDebugSrc)
!__VUE_PROD_DEVTOOLS__ && noDebug()

export const user = ref({})
export const loadComponent = (path) => {
    return new Promise((resolve, reject) => {
        loadingBar.start()
        if (!path.startsWith("/") && !path.startsWith(".")) {
            path = "./" + path
        }
        importShim(path).then(md => {
            resolve(md)
            loadingBar.success();
        }).catch(e => {
            loadingBar.fail();
            reject(e)
        })
    })
}

export function uuid() {
    const radix = 32;
    const length = 15;
    const now = Date.now();
    const prefix = now.toString(radix)
    const randomLength = length - prefix.length
    return prefix + Array.from({length: randomLength}, v => Math.floor(Math.random() * radix).toString(radix)).join('')
}

const dataCache = {}

function paramStringify(params) {
    if (!params) {
        return ""
    }
    let paramPairs = []
    Object.keys(params).forEach(k => {
        let v = params[k]
        if (v != null) {
            let constructor = v.constructor
            if (constructor == Object) {
                paramPairs.push(k + "=" + JSON.stringify(v))
            } else if (constructor == Array) {
                paramPairs.push(k + "=" + JSON.stringify(v))
            } else if (constructor == Date) {
                paramPairs.push(k + "=" + v.getTime())
            } else {
                paramPairs.push(k + "=" + encodeURIComponent(v))
            }

        }

    })
    return paramPairs.join("&")
}

export const ajax = {
    get: function (uri, params) {
        params = params || {};
        return axios.get(uri, {params: params})
    }, delete: function (uri, params) {
        params = params || {};
        return axios.delete(uri, {params: params})
    }, put: function (uri, params) {
        params = params || {};
        return axios.put(uri, params)
    }, post: function (uri, params, options = {}) {
        params = params || {};
        return axios.post(uri, params, options)
    }, formPost: function (uri, params, options = {}) {
        params = params || {};
        // let data = new FormData();
        // Object.keys(params).forEach(function (key) {
        //     let v = params[key];
        //     if (v == null) {
        //         return
        //     }
        //     if (v.constructor == Array) {
        //         for (let i = 0; i < v.length; i++) {
        //             data.append(key, v[i]);
        //         }
        //     } else {
        //         data.append(key, v);
        //     }
        // });


        return axios.post(uri, paramStringify(params), options)
    }, uploadDirect(file) {
        //
        //阿里云直传
        if (window._configs['AliyunConfig.clientDirect'] == 'true') {
            return new Promise((accept, reject) => {

                ajax.get("file/policy", {fileName: file.name}).then(r => {
                    let params = r.data
                    params.file = file
                    let req = axios.create({
                        timeout: 10000, // 请求超时事件
                        crossDomain: false
                    })

                    let data = new FormData();
                    Object.keys(params).forEach(function (key) {
                        let v = params[key];
                        if (v == null) {
                            return
                        }
                        if (v.constructor == Array) {
                            for (let i = 0; i < v.length; i++) {
                                data.append(key, v[i]);
                            }
                        } else {
                            data.append(key, v);
                        }
                    })
                    loading('文件上传中... ');
                    req({
                        url: params.host,
                        method: 'POST',
                        data: data
                    }).then((r) => {
                        console.log(r)
                    }).catch(reject).finally(r => {
                        loading.close();
                    })
                    // ajax.upload(params.host,params).then(r=>{
                    //     console.log(r)
                    // })
                    //ajax.upload()
                }).catch(reject)
            })
        } else {
            return ajax.upload("file/upload", {file: file})
        }
    }, upload: function (uri, params) {
        params = params || {};
        let data = new FormData();
        Object.keys(params).forEach(function (key) {
            let v = params[key];
            if (v == null) {
                return
            }
            if (v.constructor == Array) {
                for (let i = 0; i < v.length; i++) {
                    data.append(key, v[i]);
                }
            } else {
                data.append(key, v);
            }
        });
        return new Promise((resolve, reject) => {
            axios({
                url: uri, method: 'POST', data: data, onUploadProgress: function (progressEvent) {
                    if (progressEvent.loaded == progressEvent.total) {
                        // HeyUI.loading.close();
                    } else {
                        let complete = (progressEvent.loaded / progressEvent.total * 100 | 0) + '%'
                        //console.log('上传 ' + complete)
                        loading('文件上传中 ' + complete);
                    }
                }
            }).then(resolve)
                .catch(reject)
                .finally(() => {
                    loading.close();
                })
        })
    }, download(path, params = {}, method = 'POST') {//表单方式下载
        if (axios.defaults.baseURL) {
            path = axios.defaults.baseURL + path
        }
        params._token = sessionStorage._token;

        let ifameId = "dfi-" + uuid()
        let elemIF = document.createElement('iframe')
        elemIF.setAttribute('id', ifameId)
        elemIF.style.display = 'none';


        let form = document.createElement('form')
        form.setAttribute('action', path)
        form.setAttribute('method', method)
        form.setAttribute('target', ifameId)
        for (let k in params) {
            let input = document.createElement('input')
            input.type = 'hidden'
            input.name = k
            input.value = params[k]
            form.append(input)
        }
        // if (path.indexOf('?') > 0) {
        //     path += "&_token=" + sessionStorage._token
        // } else {
        //     path += "?_token=" + sessionStorage._token
        // }
        // elemIF.src = path;
        elemIF.append(form)
        document.body.appendChild(elemIF)
        form.submit()
        elemIF.onload = function () {
            document.body.removeChild(elemIF);
        }

    }, /**
     * 使用ajax方式下载
     * @param path 路径
     * @param fileName 文件下载后的名字
     * @param params 参数
     * @param method 请求方法,默认为GET
     */
    downloadA(path, params = {}, method = 'GET', fileName = "") {
        let reqParam = {url: path, method: method, responseType: 'arraybuffer'}
        if ("POST" == method.toUpperCase() || "PUT" == method.toUpperCase()) {
            reqParam.data = params
        } else {
            reqParam.params = params
        }
        axios.request(reqParam).then(resp => {
            if (!fileName) {
                let tempName = resp.headers["content-disposition"];
                if (tempName) {
                    tempName = tempName.split(";")[1].split("filename=")[1];
                    fileName = decodeURI(tempName);
                } else {
                    fileName = "下载文件";
                }
            }
            let data = resp.data

            //获取数据流数据
            let blob = new Blob([data]);
            var downloadElement = document.createElement("a");
            var href = window.URL.createObjectURL(blob); //创建下载的链接
            downloadElement.href = href;
            downloadElement.download = fileName; //下载后文件名
            document.body.appendChild(downloadElement);
            downloadElement.click(); //点击下载
            document.body.removeChild(downloadElement);//下载完成移除元素
            window.URL.revokeObjectURL(href);//释放掉blob对象
        })
    },
    /**
     * 数据缓存
     * @param url
     * @param defaultValue
     * @returns {*}
     */
    data(url, defaultValue) {
        let value = dataCache[url]
        if (value != null) {
            return value
        }
        value = ref(defaultValue)
        dataCache[url] = value
        Object.defineProperty(value, "load", {
            value() {
                ajax.get(url).then(r => {
                    value.value = r.data
                })
            }
        })
        value.load()
        return value;
    }, createDs(uri, pageAble = false) {
        let dataSource = reactive({
            data: [], params: {}, loading: false, method: 'post', pagination: {
                page: 0, size: 10, total: 0
            }
        })
        let self = dataSource;
        //数据载入监听
        let listeners = {}
        let transformer = (v) => v
        Object.defineProperties(dataSource, {
            param: {
                value(params) {
                    Object.assign(dataSource.params, params)
                    return dataSource;
                }
            }, setParam: {
                value(name, value) {
                    dataSource.params[name] = value;
                    return dataSource;
                }
            }, on: {
                value(name, fn) {
                    if (listeners[name]) {
                        listeners[name].push(fn)
                    } else {
                        listeners[name] = [fn]
                    }
                }
            }, clean: {
                value() {
                    dataSource.data = [];//.splice(0, self.data.length);
                    dataSource.pagination.total = 0
                }
            }, putData: {
                value(data) {
                    self.data = data
                }
            }, transform: {
                value(v) {
                    transformer = v
                }
            }, pageChange(page) {
                if (page) {
                    self.pagination.page = page.page
                    self.pagination.size = page.size
                }
                self.load()
            }, load: {
                value(params, fn) {
                    return new Promise((resolve, reject) => {
                        self.loading = true;
                        if (params) {
                            if (params instanceof Function) {
                                fn = params;
                                params = self.params;
                            }
                        } else {
                            params = self.params;
                        }
                        if (self.pagination) {
                            let pagination = self.pagination
                            params.pageNo = pagination.page;
                            params.pageSize = pagination.size;
                        }
                        if (listeners.beforeLoad) {
                            for (let fn of listeners.beforeLoad) {
                                fn(params)
                            }
                        }
                        ajax[self.method](uri, params).then(resp => {
                            self.loading = false;
                            let total = 0
                            let data = []
                            if (resp instanceof Array) {
                                total = resp.length
                                data = resp
                            } else {
                                total = resp.total || resp.recordCount
                                data = resp.data || resp.rows
                            }

                            if (listeners.load) {
                                for (let fn of listeners.load) {
                                    fn(data)
                                }
                            }

                            if (data) {
                                if (transformer) {
                                    data = transformer(data);
                                }
                                self.putData(data)
                                resolve({data, total: total})
                            }
                            if (pageAble) {
                                self.pagination.total = total || 0;
                            }

                        }).catch(e => {
                            self.clean()
                            reject(e)
                            if (listeners.error) {
                                for (let fn of listeners.error) {
                                    fn(e)
                                }
                            }
                        })
                    })
                }
            }
        })

        return dataSource;
    },
}

function dateFormat(fmt, date) {
    var o = {
        "M+": date.getMonth() + 1, //月份
        "d+": date.getDate(), //日
        "h+": date.getHours() % 12 == 0 ? 12 : date.getHours() % 12, //小时
        "H+": date.getHours(), //小时
        "m+": date.getMinutes(), //分
        "s+": date.getSeconds(), //秒
        "q+": Math.floor((date.getMonth() + 3) / 3), //季度
        "S": date.getMilliseconds() //毫秒
    };
    var week = {
        "0": "/u65e5", "1": "/u4e00", "2": "/u4e8c", "3": "/u4e09", "4": "/u56db", "5": "/u4e94", "6": "/u516d"
    };
    if (/(y+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    if (/(E+)/.test(fmt)) {
        fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? "/u661f/u671f" : "/u5468") : "") + week[date.getDay() + ""]);
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        }
    }
    return fmt;

}

function formatNumber(num, pattern) {
    var strarr = num ? num.toString().split('.') : ['0'];
    var fmtarr = pattern ? pattern.split('.') : [''];
    var retstr = '';
    // 整数部分
    var str = strarr[0];
    var fmt = fmtarr[0];
    var i = str.length - 1;
    var comma = false;
    for (var f = fmt.length - 1; f >= 0; f--) {
        switch (fmt.substr(f, 1)) {
            case '#':
                if (i >= 0) retstr = str.substr(i--, 1) + retstr;
                break;
            case '0':
                if (i >= 0) retstr = str.substr(i--, 1) + retstr; else retstr = '0' + retstr;
                break;
            case ',':
                comma = true;
                retstr = ',' + retstr;
                break;
        }
    }
    if (i >= 0) {
        if (comma) {
            var l = str.length;
            for (; i >= 0; i--) {
                retstr = str.substr(i, 1) + retstr;
                if (i > 0 && ((l - i) % 3) == 0) retstr = ',' + retstr;
            }
        } else retstr = str.substr(0, i + 1) + retstr;
    }
    retstr = retstr + '.';
    // 处理小数部分
    str = strarr.length > 1 ? strarr[1] : '';
    fmt = fmtarr.length > 1 ? fmtarr[1] : '';
    i = 0;
    for (var f = 0; f < fmt.length; f++) {
        switch (fmt.substr(f, 1)) {
            case '#':
                if (i < str.length) retstr += str.substr(i++, 1);
                break;
            case '0':
                if (i < str.length) retstr += str.substr(i++, 1); else retstr += '0';
                break;
        }
    }
    return retstr.replace(/^,+/, '').replace(/\.$/, '');
}

export const format = function (value, format) {
    if (arguments.length == 2) {
        if (format.startsWith("#") || format.startsWith("0")) {
            return formatNumber(value, format);
        } else {
            if (value == null) {
                return "";
            }
            if (value instanceof Date) {

            } else {
                value = new Date(value);
            }
            return dateFormat(format, value);
        }
    }
    format = value;
    if (format.startsWith("#") || format.startsWith("0")) {
        return function (val) {
            return formatNumber(val, format);
        }
    } else {
        return function (val) {
            if (val == null) {
                return "";
            }
            if (val instanceof Date) {

            } else {
                val = new Date(val);
            }
            return dateFormat(format, val);
        }
    }

}

export let DataSource = function (uri, pageable) {
    this.data = ref([]);
    this.params = reactive({});
    this.loading = false;
    this.uri = uri;
    this.method = "post"
    let self = this;
    if (pageable) {
        this.pagination = reactive({
            page: 0, size: 10, total: 0
        })
    }
    this.param = function (params) {
        Object.assign(self.params, params)
        return this;
    }
    this.setParam = function (name, value) {
        this.params[name] = value;
        return this;
    }
    //数据载入监听
    let listeners = {}
    /**
     * 数据载入监听
     * @param name beforeLoad load error
     * @param fn
     */
    this.on = function (name, fn) {
        if (listeners[name]) {
            listeners[name].push(fn)
        } else {
            listeners[name] = [fn]
        }
    }
    /**
     * 清空数据
     */
    this.clean = function () {
        self.data.value = [];//.splice(0, self.data.length);
    }
    /**
     * 把数据放入到data中,不直接通过data赋值,这样vue可以感知到数据变化
     * @param data
     */
    this.putData = function (data) {
        self.data.value = data
        // self.data.splice(0, self.data.length);
        // self.data.push(...data);
    }
    /**
     *
     * @param params 查询参数
     * @param fn 数据转换回调
     * @returns {DataSource}
     */
    this.load = function (params, fn) {
        self.loading = true;
        if (params) {
            if (params instanceof Function) {
                fn = params;
                params = self.params;
            }
        } else {
            params = self.params;
        }
        if (self.pagination) {
            let pagination = self.pagination
            params.pageNo = pagination.page;
            params.pageSize = pagination.size;
        }
        if (listeners.beforeLoad) {
            for (let fn of listeners.beforeLoad) {
                fn(params)
            }
        }

        ajax[self.method](self.uri, params).then(resp => {
            self.loading = false;
            if (listeners.load) {
                for (let fn of listeners.load) {
                    fn(resp.data)
                }
            }
            if (resp.data) {
                let data = resp.data;
                if (fn) {
                    data = fn(data);
                }
                self.putData(data)
            }
            if (self.pagination && resp.total) {
                self.pagination.total = resp.total;
            }
        }).catch(e => {
            self.clean()
            if (listeners.error) {
                for (let fn of listeners.error) {
                    fn(e)
                }
            }
        })
        return this;
    }
}

//init vue
let asyncComp = (path) => defineAsyncComponent(() => loadComponent(path))
const getDict = function () {
    const dictCache = {}
    const dictHandler = {}
    const refCaches = {}
    let tempReq = {}
    let timer = null


    const setDict = heyuiConfig.addDict = function (type, values) {
        if (values.constructor == Promise) {
            dictCache[type] = []
            values.then(r => {
                setDict(type, r.data)
            })
            return
        }
        let dict = values
        let dictMap = {}
        const keyField = heyuiConfig.getOption('dict', 'keyName');
        const titleField = heyuiConfig.getOption('dict', 'titleName');
        if (values.constructor != Array) {
            dictMap = values
            dict = Object.entries(values).map(it => ({[keyField]: it[0], [titleField]: it[1]}))
        } else {
            for (let it of values) {
                dictMap[it[keyField]] = it[titleField]
            }
        }
        let oldCache = dictCache[type]
        if (oldCache) {
            oldCache.splice(0, oldCache.length)
            oldCache.push(...dict)
            dict = oldCache//dictCache[type]
        }
        Object.defineProperty(dict, 'mapping', {
            writable: true,
            value(value) {
                if (value == undefined) {
                    return undefined
                }
                let v = dictMap[value]
                return v || value
            }
        })
        dictCache[type] = dict
    }

    heyuiConfig.initDict = function (obj) {
        const keyField = this.getOption('dict', 'keyName');
        const titleField = this.getOption('dict', 'titleName');
        for (let k in obj) {
            let data = obj[k]
            if (!data) {
                continue
            }
            if (data.constructor == Array) {
                setDict(k, data)
            } else {
                setDict(k, Object.entries(data).map(it => ({[keyField]: it[0], [titleField]: it[1]})))
            }
        }
    }

    //合并字典请求
    function loadDict(type, handler, error, finalHandler) {
        if (timer == null) {
            timer = setTimeout(() => {

                let reqMap = tempReq;
                tempReq = {}
                timer = null

                let types = Object.keys(reqMap);
                ajax.post("system/dicts", types).then(r => {
                    let datas = r.data
                    for (let k of types) {
                        let data = datas[k] || []
                        for (let h of reqMap[k]) {
                            h[0](data)
                        }
                    }
                }).catch(e => {
                    for (let k of types) {
                        for (let h of reqMap[k]) {
                            h[1](e)
                        }
                    }
                }).finally(() => {
                    for (let k of types) {
                        for (let h of reqMap[k]) {
                            h[2]()
                        }
                    }
                })
            }, 10)
        }
        let handlers = tempReq[type]
        if (handlers == null) {
            handlers = tempReq[type] = []
        }
        handlers.push([handler, error, finalHandler])
    }

    //加载字典，带缓存
    function _getDict(type, value) {
        if (arguments.length == 2) {
            let typeRefs = refCaches[type]
            if (typeRefs == null) {
                typeRefs = refCaches[type] = {}
            }
            let dataRef = typeRefs[value]
            if (dataRef == null) {
                dataRef = typeRefs[value] = ref()
                dataRef.toString = function () {
                    return dataRef.value || ""
                }
            }
            _getDict(type).then(r => {
                dataRef.value = r.mapping(value)
            })
            return dataRef
        }
        let dict = dictCache[type]
        if (dict) {
            return new Promise(reslove => reslove(dict))
        } else {
            let handler = dictHandler[type]
            if (handler) {
                return new Promise((resolve, reject) => {
                    handler.push([resolve, reject])
                })
            }
            return new Promise((resolve, reject) => {
                handler = dictHandler[type] = []
                handler.push([resolve, reject])
                loadDict(type, r => {
                    //dictCache[type] = r
                    setDict(type, r)
                    handler.forEach(it => it[0](r))
                }, e => {
                    handler.forEach(it => it[1](e))
                }, () => {
                    delete dictHandler[type]
                })
            })
        }
    }

    return _getDict;
}()


function useHeyUI(instance) {
    const config = heyuiConfig
    const dictMapping = config.dictMapping
    config.dictMapping = function (value, key, connector) {
        return getDict(key, value);
    }
    config.getDict = getDict

    let tempComponent = instance.component
    let coms = {
        AutoComplete: true,
        Checkbox: true,
        DropdownMenu: true,
        Radio: true,
        Select: true,
        Steps: true,
        SwitchList: true,
        Tabs: true
    }

    instance.component = function (name, def) {
        if (coms[name]) {
            if (def.props.dict) {
                //定义动态字典
                def.setup = function (props, context) {
                    if (props.dict) {
                        let data = ref([])
                        getDict(props.dict).then(d => {
                            data.value = d//.push(...d)
                        })
                        return {
                            datas: data,
                            dict: undefined
                        }
                    }
                }
            }
        }
        return tempComponent(name, def)
    }
    instance.use(HeyUI, {i18n})
    instance.component = tempComponent
}

function regComponents(instance) {
    //let Props = instance.config.globalProperties

    useHeyUI(instance)
    instance.use(ElementPlus, {})
    // instance.use(HeyUI)
    // instance.use(HeyUI)

    // Props.$axios = axios
    // Props.$ajax = ajax

    //导入组件
    // instance.component('FileInput', asyncComp('coms/FileInput.vue'))
    // instance.component('MonacoEditor', asyncComp('coms/MonacoEditor.vue'))
    // instance.component('Chart', asyncComp('coms/Echarts.vue'))
    // instance.component('RichTextEditor', asyncComp('coms/RichTextEditor.vue'))
    // instance.component('Prompt', asyncComp('coms/Prompt.vue'))
    // instance.component('ImageViewer', asyncComp('coms/ImageViewer.vue'))
    // instance.component('DataSet', asyncComp('coms/DataSet.vue'))
    // instance.component('Dialog', asyncComp('coms/Dialog.vue'))
    // instance.component('FileUploader', asyncComp('coms/Uploader.vue'))
    //
    instance.component('Lang', function (props) {
        return $t(props.zh, props.en)
    })
    instance.component('QRCode', asyncComp('js/lib/qrcode-next.js'))
    const components = ['TableControl', 'Chart',
        'EChart', 'FileUploader',
        'FileInput', 'FileImport', 'MonacoEditor',
        'RichTextEditor', 'FiledGroup',
        'Prompt', 'ImageViewer',
        'DataSet', 'DataTable',
        'LinkButtons', 'Dialog',
        'Legend', 'Dict',
        'File', 'AMap', 'Divider',
        'AMapInput', 'FilePreview', 'PagePanel', 'SubPage', 'MultiDictInput', 'CodesInput']
    for (let comp of components) {
        instance.component(comp, asyncComp('coms/' + comp + '.vue'))
    }
    const Page = defineComponent({
        props: {
            href: {
                type: String,
                default: ''
            },
            name: {
                type: String,
                default: ''
            }
        },
        setup(props, context) {
            const route = useRoute()
            let {href, name} = props;
            const defaultSlot = context.slots.default;
            const ContentComponent = href ? defineAsyncComponent(() => loadComponent(href)) : defaultSlot
            const isOpen = ref(false)
            const params = ref(null)
            if (name && !name.startsWith('#')) {
                name = '#' + name
            }
            const setParams = (v) => {
                isOpen.value = v.hash == name
                params.value = route.query
            }
            watch(route, setParams)
            setParams(route)
            return () => isOpen.value ? h(ContentComponent, {params: params}) : null
        }
    })
    instance.component('Page', Page)
    const Pages = defineComponent({
        setup(props, context) {
            const route = useRoute()
            const compMap = {}
            const defaultSlot = context.slots.default;
            defaultSlot().forEach((it, idx) => {
                let props = it.props || {}
                let {href, path} = props
                delete props.href
                delete props.path
                if (!path) {
                    path = ""
                }
                if (href) {
                    compMap[path] = h(defineAsyncComponent(() => loadComponent(href)), props)
                } else {
                    compMap[path] = h(() => defaultSlot()[idx])
                }
            })
            return (ctx) => {
                let props = ctx.props || {}
                if (Object.keys(route.query).length > 0) {
                    props.params = route.query
                }
                return h(compMap[route.hash], props)
            }
        }
    })
    instance.component('Pages', Pages)
    instance.component('Password', defineComponent({
        props: {
            modelValue: String,
            placeholder: {
                type: String,
                default: ''
            }
        },
        data() {
            return {
                inputType: 'password',
                iconClass: 'jeicon-eye-close'
            }
        },
        methods: {
            iconClick() {
                if (this.inputType == 'password') {
                    this.inputType = 'text'
                    this.iconClass = 'jeicon-eye'
                } else {
                    this.inputType = 'password'
                    this.iconClass = 'jeicon-eye-close'
                }
            }
        },
        render() {
            return h('span', {
                style: 'position: relative;display: inline-block;'
            }, [
                h('input', {
                    class: 'h-input ',
                    placeholder: this.placeholder,
                    value: this.modelValue,
                    onInput: (e) => this.$emit('update:modelValue', e.target.value),
                    type: this.inputType,
                    style: 'padding-right: 23px;width:100%;'
                }),
                h('i', {
                    onClick: this.iconClick,
                    class: this.iconClass,
                    style: 'cursor: pointer;position: absolute;right: 5px;top: 0;display: flex;height: 100%;align-items: center;font-size: 10px;color: #a8abb2;'
                })
            ])
        }
    }))
}

export function alert(title, info) {
    return new Promise(resolve => {
        modal({
            title: title, content: info, closeOnMask: false, middle: true, buttons: [{
                type: 'ok', name: '确认', color: 'primary'
            }], events: {
                ok: (modal) => {
                    modal.close();
                    resolve()
                }
            }
        })
    })
}

function asGetter(propNameOrFun) {
    if (typeof propNameOrFun == 'function') {
        return propNameOrFun
    }
    return (it) => it[propNameOrFun]
}

export const usePage = () => {
    const router = useRouter()
    return {
        show(name = '', params = {}) {
            if (name && !name.startsWith("#")) {
                name = '#' + name
            }
            router.push({hash: name, query: params})
        }
    }
}

export const util = {
    /**
     * 数组转换为map
     * @param list 数组
     * @param getId
     * @param getValue
     * @returns {{}}
     */
    asMap(list, getId = (it, idx) => idx, getValue = it => it) {
        getId = asGetter(getId)
        getValue = asGetter(getValue)
        let map = {}
        for (let i = 0; i < list.length; i++) {
            let item = list[i]
            map[getId(item, i)] = getValue(item)
        }
        return map;
    }, /**
     * 数组转换为树,parentId为0，false，null的或者获取不到parent的节点转为根节点
     * @param list
     * @param getId
     * @param getParentId
     * @param getValue
     * @returns {*[]}
     */
    asTree(list, getId = it => it, getParentId = it => null, getValue = it => it) {
        let ret = []
        let root = {
            children: ret
        }
        getId = asGetter(getId)
        getParentId = asGetter(getParentId)
        getValue = asGetter(getValue)
        let map = util.asMap(list, getId, getValue)
        for (let it of list) {
            let parentId = getParentId(it)
            let _id = getId(it)
            let data = map[_id]
            if (!parentId) {
                ret.push(data)
                continue
            }
            let parent = map[parentId]
            if (parent) {
                if (!parent.children) {
                    parent.children = []
                }
                parent.children.push(data)
            } else {
                ret.push(data)
            }
        }
        return ret
    }, fields(obj, fields) {
        let ret = {}
        if (obj == null || fields == null) {
            return ret;
        }
        for (let f of fields.split(',')) {
            if (obj.hasOwnProperty(f)) {
                ret[f] = obj[f]
            }
        }
        return ret;
    }, fold(obj, fields) {//将对应字段的数组用','拼接
        if (obj == null || fields == null) {
            return obj;
        }
        if (!fields) {
            return obj;
        }
        let result = {};
        Object.assign(result, obj)
        for (let f of fields.split(',')) {
            if (obj[f]) {
                result[f] = obj[f].join(',')
            }
        }
        return result;
    }, unfold(obj, fields) {//将对应字段的字符串用','拆封
        if (obj == null || fields == null) {
            return obj;
        }
        if (!fields) {
            return obj;
        }
        let result = {};
        Object.assign(result, obj)
        for (let f of fields.split(',')) {
            if (obj[f]) {
                result[f] = obj[f].split(',')
            }
        }
        return result;
    }, mapToKv(data) {
        if (!data) {
            return []
        }
        let r = []
        let keys = Object.keys(data).sort((a, b) => a > b ? 1 : -1);
        for (let k of keys) {
            r.push({key: k, title: data[k]})
        }
        return r;
    }, clean(value) {//清空数据
        if (value == null) {
            return;
        }
        if (value instanceof Array) {
            value.splice(0, value.length)
        } else {
            let _isReactive = isReactive(value)
            Object.keys(value).forEach(k => {
                let v = value[k]
                if (v == null) {
                    return
                }
                if (v instanceof Array) {
                    //如果对象本身是响应的，直接赋值
                    if (_isReactive) {
                        value[k] = []
                    } else {
                        v.splice(0, v.length)
                    }
                } else {
                    value[k] = null
                }
            })
        }
    }, format: format, onMountedOrActivated(hook) {
        let mounted;

        onMounted(() => {
            hook();
            nextTick(() => {
                mounted = true;
            });
        });
        onActivated(() => {
            if (mounted) {
                hook();
            }
        });
    }, useEventListener(target, type, listener) {

        const passive = false, capture = false;

        let attached = false;

        const add = (target) => {
            const element = unref(target);

            if (element && !attached) {
                element.addEventListener(type, listener, {capture, passive});
                attached = true;
            }
        };

        const remove = (target) => {
            const element = unref(target);

            if (element && attached) {
                element.removeEventListener(type, listener, capture);
                attached = false;
            }
        };

        onUnmounted(() => remove(target));
        onDeactivated(() => remove(target));
        util.onMountedOrActivated(() => add(target));
        if (isRef(target)) {
            watch(target, (val, oldVal) => {
                remove(oldVal);
                add(val);
            });
        } else {
            add(target)
        }
        return () => remove(target)
    }, isObject(value) {
        const type = typeof value
        return value != null && (type === 'object' || type === 'function')
    }, debounce(func, wait, options) {
        let root = window
        let lastArgs, lastThis, maxWait, result, timerId, lastCallTime

        let lastInvokeTime = 0
        let leading = false
        let maxing = false
        let trailing = true

        // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
        const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')

        if (typeof func !== 'function') {
            throw new TypeError('Expected a function')
        }
        wait = +wait || 0
        if (util.isObject(options)) {
            leading = !!options.leading
            maxing = 'maxWait' in options
            maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
            trailing = 'trailing' in options ? !!options.trailing : trailing
        }

        function invokeFunc(time) {
            const args = lastArgs
            const thisArg = lastThis

            lastArgs = lastThis = undefined
            lastInvokeTime = time
            result = func.apply(thisArg, args)
            return result
        }

        function startTimer(pendingFunc, wait) {
            if (useRAF) {
                root.cancelAnimationFrame(timerId)
                return root.requestAnimationFrame(pendingFunc)
            }
            return setTimeout(pendingFunc, wait)
        }

        function cancelTimer(id) {
            if (useRAF) {
                return root.cancelAnimationFrame(id)
            }
            clearTimeout(id)
        }

        function leadingEdge(time) {
            // Reset any `maxWait` timer.
            lastInvokeTime = time
            // Start the timer for the trailing edge.
            timerId = startTimer(timerExpired, wait)
            // Invoke the leading edge.
            return leading ? invokeFunc(time) : result
        }

        function remainingWait(time) {
            const timeSinceLastCall = time - lastCallTime
            const timeSinceLastInvoke = time - lastInvokeTime
            const timeWaiting = wait - timeSinceLastCall

            return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting
        }

        function shouldInvoke(time) {
            const timeSinceLastCall = time - lastCallTime
            const timeSinceLastInvoke = time - lastInvokeTime

            // Either this is the first call, activity has stopped and we're at the
            // trailing edge, the system time has gone backwards and we're treating
            // it as the trailing edge, or we've hit the `maxWait` limit.
            return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
        }

        function timerExpired() {
            const time = Date.now()
            if (shouldInvoke(time)) {
                return trailingEdge(time)
            }
            // Restart the timer.
            timerId = startTimer(timerExpired, remainingWait(time))
        }

        function trailingEdge(time) {
            timerId = undefined

            // Only invoke if we have `lastArgs` which means `func` has been
            // debounced at least once.
            if (trailing && lastArgs) {
                return invokeFunc(time)
            }
            lastArgs = lastThis = undefined
            return result
        }

        function cancel() {
            if (timerId !== undefined) {
                cancelTimer(timerId)
            }
            lastInvokeTime = 0
            lastArgs = lastCallTime = lastThis = timerId = undefined
        }

        function flush() {
            return timerId === undefined ? result : trailingEdge(Date.now())
        }

        function pending() {
            return timerId !== undefined
        }

        function debounced(...args) {
            const time = Date.now()
            const isInvoking = shouldInvoke(time)

            lastArgs = args
            lastThis = this
            lastCallTime = time

            if (isInvoking) {
                if (timerId === undefined) {
                    return leadingEdge(lastCallTime)
                }
                if (maxing) {
                    // Handle invocations in a tight loop.
                    timerId = startTimer(timerExpired, wait)
                    return invokeFunc(lastCallTime)
                }
            }
            if (timerId === undefined) {
                timerId = startTimer(timerExpired, wait)
            }
            return result
        }

        debounced.cancel = cancel
        debounced.flush = flush
        debounced.pending = pending
        return debounced
    }, throttle(func, wait, options) {
        let leading = true
        let trailing = true

        if (typeof func !== 'function') {
            throw new TypeError('Expected a function')
        }
        if (util.isObject(options)) {
            leading = 'leading' in options ? !!options.leading : leading
            trailing = 'trailing' in options ? !!options.trailing : trailing
        }
        return util.debounce(func, wait, {
            leading, trailing, 'maxWait': wait
        })
    }, dialogModel(v) {
        return reactive({
            value: v, isOpen: false, open(v) {
                this.value = v
                this.isOpen = true
            }
        })
    }, exportExcel(data, name, type = "aoa") {
        importShim('xlsx').then(function (XLSX) {
            let wb = XLSX.utils.book_new();
            let sheet = XLSX.utils[type + "_to_sheet"](data)
            XLSX.utils.book_append_sheet(wb, sheet, 'sheet1');
            XLSX.writeFile(wb, name);
        })
    },
    /**
     * 导入Excel文件
     * @param file 需要导入的File对象
     * @param asObj 是否作为对象数组输出，否则为二维数组
     * @return {Promise<unknown>}
     */
    importExcel(file,asObj=false){
        return new Promise((resolve, reject)=>{
            importShim('xlsx').then(function (XLSX) {
                const reader = new FileReader();
                reader.readAsArrayBuffer(file);
                reader.onload = function (e) {
                    const data = e.target.result;
                    const excel = XLSX.read(data, {
                        type: 'binary'
                    });
                    let result={}
                    let option=asObj?{}:{header:1}
                    for (let i = 0; i < excel.SheetNames.length; i++) {
                        let name=excel.SheetNames[i];
                        let data = XLSX.utils.sheet_to_json(excel.Sheets[name],option);
                        result[name]=data
                    }
                    resolve(result)
                }
                reader.onerror=function (e){
                    reject(e)
                }
            })
        })
    }
    ,
    onProcessSse(handler) {
        let loaded = 0
        let buffer = ''
        let event = {}
        return (progressEvent) => {
            const xhr = progressEvent.event.target
            const {responseText} = xhr
            const value = responseText.substring(loaded)
            loaded = responseText.length
            buffer += value
            const lines = buffer.split('\n');
            buffer = lines.pop();
            lines.forEach(function (line) {
                if (line == '') {
                    handler(event)
                    event = {}
                } else {
                    if (line.startsWith("event:")) {
                        event.event = line.substring(6)
                    } else if (line.startsWith("data:")) {
                        event.data = line.substring(5)
                    } else if (line.startsWith("id:")) {
                        event.id = line.substring(3)
                    } else if (line.startsWith(":")) {
                        event.comment = line.substring(1)
                    } else if (line.startsWith("retry:")) {
                        event.retry = line.substring(6)
                    }
                }
            })
        }
    }
};
let routed = {}
let app;

export function hasPermission(code) {
    //let store = Vuex.useStore()
    let _user = user.value
    if (_user.username == 'admin') {
        return true
    }
    if (!code) {
        return true
    }
    let permissions = _user.permissions
    if (!permissions) {
        return false
    } else {
        let fun = new Function("p", "return " + code.replace(/([\w\.]+)/g, "p['$1']"))
        let pObj = {};
        permissions.forEach(it => pObj[it] = true)
        return fun(pObj)//permissions.indexOf(code) > -1
    }
}

export function hasRole(code) {
    let _user = user.value
    if (_user.username == 'admin') {
        return true
    }
    if (!code) {
        return true
    }
    let roles = _user.roles
    if (!roles) {
        return false
    } else {
        let fun = new Function("p", "return " + code.replace(/([\w\.]+)/g, "p['$1']"))
        let pObj = {};
        roles.forEach(it => pObj[it] = true)
        return fun(pObj)//permissions.indexOf(code) > -1
        // return roles.indexOf(code) > -1
    }
}

export function auth(value = {permissions: '', roles: ''}) {
    if (!user.value.username) {
        location.hash = "/login"
    } else if (!framework.hasPermission(value.permissions)) {
        location.hash = "/login"
    } else if (!framework.hasRole(value.roles)) {
        location.hash = "/login"
    }
}

export function init(idx, options) {
    if (options && options.baseURL) {
        axios.defaults.baseURL = options.baseURL
    }
    let initEvent = new Event('frameworkInit')
    initEvent.framework = framework
    dispatchEvent(initEvent)

    let idxPage = {
        template: '<router-view/>'
    }
    Object.assign(idxPage, idx)
    app = createApp(idxPage);
    let perLoader = document.getElementById("per-loader");
    if (perLoader) {
        perLoader.style.display = 'none'
    }
    regComponents(app)
    let createdEvent = new Event('appCreated')
    createdEvent.data = app
    // app.filter('format', format)
    dispatchEvent(createdEvent)
    //app.mount(target)
    return app
}

export function useApp() {
    return app;
}

export function getBranches() {
    return framework.getBranches.apply(framework, arguments)
}

export function cleanBranches() {
    return framework.cleanBranches.apply(framework, arguments)
}

addEventListener('frameworkInit', ({framework}) => {

    const branches = ref([])
    const branchTree = ref([])
    let branchMap = {}
    let loadStatus = 0
    let listeners = []
    Object.defineProperty(framework, 'cleanBranches', {
        value() {
            branches.value = []
            branchTree.value = []
            branchMap = {}
            loadStatus = 0
        }
    })
    const mapper = (v) => {
        let branch = branchMap[v]
        return branch ? branchMap[v].fullName : v
    }
    mapper.onload = function (fn) {
        if (loadStatus == 2) {
            fn(branches)
        }
        listeners.push(fn)
    }
    Object.defineProperty(framework, 'getBranches', {
        value(type) {
            if (loadStatus == 0) {
                loadStatus = 1
                framework.ajax.get("branch/list?enable=1").then(r => {
                    // branchOption.datas = r.data;
                    r = r.data
                    branches.value = r
                    let util = framework.util
                    Object.assign(branchMap, util.asMap(r, it => it.sn))
                    branchTree.value = util.asTree(r, "id", "pid")
                    loadStatus = 2
                    for (let fn of listeners) {
                        fn(branches)
                    }
                    listeners = []
                }).catch(e => {
                    loadStatus = 0
                    console.error(e)
                })
            }
            switch (type) {
                case "map":
                    return branchMap
                case "tree":
                    return branchTree
                case "mapper":
                    return mapper
                default:
                    return branches
            }
        }
    })
})

function useForm(formData, formRef) {
    const data = reactive(formData)
    const loading = ref(false)
    const showModel = ref(false)
    return {
        data,
        showModel,
        loading,
        show(data) {
            showModel.value = true
            util.clean(data)
            Object.assign(data, data)
        },
        reset() {
            util.clean(data)
            Object.assign(data, formData)
        },
        submit(url) {
            let validResult = formRef.value.valid();
            if (validResult.result) {
                loading.value = true
                ajax.post(url, data).then(r => {

                }).finally(e => {
                    loading.value = false
                })
            }
        },
        load(url, params) {
            loading.value = true
            ajax.get(url, params).then(r => {
                util.clean(data)
                Object.assign(data, r.data)
            }).finally(() => {
                loading.value = false
            })
        }
    }
}

const _keyStr =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";

// public method for encoding
/**
 *
 * @param input {Uint8Array}
 * @return {string}
 */
function base64Encode(input) {
    let ret = btoa(String.fromCharCode.apply(null, input))
    if (ret) {
        return ret.replaceAll('+', '-').replaceAll('/', '_')//'-', '_'
    }
    // let output = "";
    // let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    // let i = 0;
    //
    // while (i < input.length) {
    //     chr1 = input[i++];
    //     chr2 = input[i++];
    //     chr3 = input[i++];
    //
    //     enc1 = chr1 >> 2;
    //     enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
    //     enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
    //     enc4 = chr3 & 63;
    //
    //     if (isNaN(chr2)) {
    //         enc3 = enc4 = 64;
    //     } else if (isNaN(chr3)) {
    //         enc4 = 64;
    //     }
    //
    //     output =
    //         output +
    //         _keyStr.charAt(enc1) +
    //         _keyStr.charAt(enc2) +
    //         _keyStr.charAt(enc3) +
    //         _keyStr.charAt(enc4);
    // }
    // return output;
};

function leftPad(s, char, len) {
    return new Array(len - s.length + 1).join(char) + s
}

function sign(v) {
    return base64Encode(ConvertHex.toBytes(md5(v)))
}

export function genToken(value, salt, expire) {
    let time = parseInt(Date.now() / 1000) + expire;
    let t = leftPad(time.toString(36), '0', 7);
    let v = value + time + salt;
    return t + sign(v) + base64Encode(ConvertUtf8.toBytes(value));
}

//获取浏览器Canvas指纹
const getCanvasFp = function (options) {
    options = options ? options : {};
    var result = []
    // Very simple now, need to make it more complex (geo shapes etc)
    var canvas = document.createElement('canvas')
    canvas.width = 2000
    canvas.height = 200
    canvas.style.display = 'inline'
    var ctx = canvas.getContext('2d')
    // detect browser support of canvas winding
    // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
    // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/canvas/winding.js
    ctx.rect(0, 0, 10, 10)
    ctx.rect(2, 2, 6, 6)
    result.push('canvas winding:' + ((ctx.isPointInPath(5, 5, 'evenodd') === false) ? 'yes' : 'no'))

    ctx.textBaseline = 'alphabetic'
    ctx.fillStyle = '#f60'
    ctx.fillRect(125, 1, 62, 20)
    ctx.fillStyle = '#069'
    // https://github.com/Valve/fingerprintjs2/issues/66
    if (options.dontUseFakeFontInCanvas) {
        ctx.font = '11pt Arial'
    } else {
        ctx.font = '11pt no-real-font-123'
    }
    ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 2, 15)
    ctx.fillStyle = 'rgba(102, 204, 0, 0.2)'
    ctx.font = '18pt Arial'
    ctx.fillText('Cwm fjordbank glyphs vext quiz, \ud83d\ude03', 4, 45)

    // canvas blending
    // http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
    // http://jsfiddle.net/NDYV8/16/
    ctx.globalCompositeOperation = 'multiply'
    ctx.fillStyle = 'rgb(255,0,255)'
    ctx.beginPath()
    ctx.arc(50, 50, 50, 0, Math.PI * 2, true)
    ctx.closePath()
    ctx.fill()
    ctx.fillStyle = 'rgb(0,255,255)'
    ctx.beginPath()
    ctx.arc(100, 50, 50, 0, Math.PI * 2, true)
    ctx.closePath()
    ctx.fill()
    ctx.fillStyle = 'rgb(255,255,0)'
    ctx.beginPath()
    ctx.arc(75, 100, 50, 0, Math.PI * 2, true)
    ctx.closePath()
    ctx.fill()
    ctx.fillStyle = 'rgb(255,0,255)'
    // canvas winding
    // http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
    // http://jsfiddle.net/NDYV8/19/
    ctx.arc(75, 75, 75, 0, Math.PI * 2, true)
    ctx.arc(75, 75, 25, 0, Math.PI * 2, true)
    ctx.fill('evenodd')

    if (canvas.toDataURL) {
        result.push('canvas fp:' + canvas.toDataURL())
    }
    return md5(canvas.toDataURL())
}
const canvas_fp = getCanvasFp()

/**
 * 保存本地token
 * @param un userType&username
 * @param uk userKey
 * @param _token token
 * @param isRemember 是否记住我
 */
export function saveToken(un, uk, _token = null, isRemember = false) {
    sessionStorage.uk = uk
    sessionStorage.un = un
    // if (_token) {
    //     sessionStorage._token = _token
    // }
    if (isRemember) {
        let today = Math.floor(Date.now() / (1000 * 60 * 60 * 24))
        //记录登录数据
        let rememberData = {
            uk,
            un,
            count: 1,
            login: today,
            infinite: false,//永久登录
            expires: today + 7
        }
        //存储加密签名
        localStorage.rmb_tk = encryptb(JSON.stringify(rememberData), canvas_fp)
    }
}

//登录token过期时间 180s=3min
const tokenSessionExpire = 180
//记住我7天内自动登录
const rememberDay = 7

function reSetToken() {
    if (sessionStorage.un && sessionStorage.uk) {
        sessionStorage._token = genToken(sessionStorage.un, sessionStorage.uk, tokenSessionExpire)
        return true;
    } else if (localStorage.rmb_tk) {
        try {
            let today = Math.floor(Date.now() / (1000 * 60 * 60 * 24))
            let rememberDataJSON = decryptb(localStorage.rmb_tk, canvas_fp).replaceAll('\x0E', '')
            let data = JSON.parse(rememberDataJSON)
            //永久记住登录
            if (data.infinite) {
                sessionStorage.un = data.un
                sessionStorage.uk = data.uk
                sessionStorage._token = genToken(sessionStorage.un, sessionStorage.uk, tokenSessionExpire)
                return true
            } else if (today < data.expires) {
                //未过期
                sessionStorage.un = data.un
                sessionStorage.uk = data.uk
                sessionStorage._token = genToken(sessionStorage.un, sessionStorage.uk, tokenSessionExpire)
                //更新登录信息
                if (data.login == today) {
                    //当天已经登录过
                    return true
                }
                //昨日已登录
                if (data.login == today - 1) {
                    //累计登录天数
                    data.count++
                } else {
                    //重新计数
                    data.count = 1
                }
                //今日首次登录
                data.login = today
                //如果已经连续登录7天
                if (data.count >= 7) {
                    //连续7天登录，永久记住
                    data.infinite = true
                } else {
                    data.expires = today + 7
                }
                //更新token
                localStorage.rmb_tk = encryptb(JSON.stringify(data), canvas_fp)
            } else {//已经过期
                return false
            }
        } catch (e) {
            console.log(e)
            localStorage.removeItem("rmb_tk")
            return false
        }
    }
    // else if (localStorage.un&&localStorage.uk&&localStorage.intime){
    //     if (localStorage.intime < new Date().getTime()){
    //         localStorage.clear()
    //         return false
    //     }
    //     setExpire()
    //     sessionStorage._token=genToken(localStorage.un,localStorage.uk,1800)
    //     return true;
    // }
    return false
}

function setExpire() {
    let a = []
    if (localStorage.loginDate) {
        a = JSON.parse(localStorage.loginDate)
    }
    //年月日
    let currentDate = new Date().getFullYear() + "-" + (new Date().getMonth() + 1) + "-" + new Date().getDate()
    //过滤重复的时间
    if (!a.includes(currentDate)) {
        a.push(currentDate)
    }
    let b = 1000 * 60 * 60 * 24 * 7
    //不是近七天，删掉
    a = a.filter(item => {
        return new Date(item + " 00:00:00").getTime() + b >= new Date().getTime();
    })
    localStorage.loginDate = JSON.stringify(a)
    //超过七个，则满足近七天天天登录
    if (a.length >= 7) {
        localStorage.intime = new Date().getTime() + (1000 * 60 * 60 * 24 * 365)//一年有效
    } else {
        localStorage.intime = new Date().getTime() + (1000 * 60 * 60 * 24 * 7)//七天有效
    }
}

// const getCacheToken=useCache(()=>{
//     //reSetToken()
//     let token = sessionStorage['_token'];
//     if (token) {
//         let t = parseInt(token.substr(0, 7), 36)
//         let n = Math.floor(Date.now() / 1000)
//         if (n > t - 60) {
//             reSetToken()
//             token = sessionStorage['_token']
//         }
//         return token
//     } else if (reSetToken()) {
//         token = sessionStorage['_token']
//         return token
//     } else {
//         return null
//     }
// },tokenSessionExpire-60)
export function getToken() {
   // return getCacheToken()
    let token = sessionStorage['_token'];
    if (token) {
        let t = parseInt(token.substr(0, 7), 36)
        let n = Math.floor(Date.now() / 1000)
        if (n > t - 60) {
            reSetToken()
            token = sessionStorage['_token']
        }
        return token
    } else if (reSetToken()) {
        token = sessionStorage['_token']
        return token
    } else {
        return null
    }
}

//修改Fetch
const _originFetch = window.fetch
const urlMatcher = /^(https?:)?\/\//
window.fetch = function (url, options = {}) {
    if (!url || urlMatcher.test(url)) {
        return _originFetch(url, options);
    }
    const token = getToken();
    if (token) {
        if (!options.headers) {
            options.headers = {}
        }
        options.headers.token = token;
    }
    return _originFetch(url, options);
}
const framework = {
    version: '1.0', user, ajax, axios, loadComponent, uuid, hasPermission, //可以使用表达式 a.a&&(a.b||c.d)
    hasRole, auth, util, format: format, DataSource: DataSource, init,
    get app() {
        return app;
    }
}
export const getApp = () => app;
const escapeStack = []
const escapeListeners = (e) => {
    if (e.key === "Escape") {
        if (escapeStack.length >= 0) {
            escapeStack[escapeStack.length - 1](e)
        }
    }
};

export function useEscapeStack(cb) {
    onMounted(() => {
        escapeStack.push(cb)
        if (escapeStack.length == 1) {
            document.addEventListener('keydown', escapeListeners)
        }
    })
    onUnmounted(() => {
        escapeStack.pop()
        if (escapeStack.length == 0) {
            document.removeEventListener('keydown', escapeListeners)
        }
    })
}

export function useKeyDown() {
    let listeners = {}
    let listener = (e) => {
        let fns = listeners[e.key]
        if (fns) {
            fns.forEach(fn => fn(e))
        }
    }
    onMounted(() => {
        document.addEventListener('keydown', listener)
    })
    onUnmounted(() => {
        document.removeEventListener('keydown', listener)
    })
    return {
        on(key, callback) {
            let fns = listeners[key]
            if (!fns) {
                fns = listeners[key] = []
            }
            fns.push(callback)
        }
    }
}

export default framework

function escapeXml(unsafe) {
    return unsafe.replace(/[<>&'"]/g, function (c) {
        switch (c) {
            case '<':
                return '&lt;';
            case '>':
                return '&gt;';
            case '&':
                return '&amp;';
            case '\'':
                return '&apos;';
            case '"':
                return '&quot;';
        }
    });
}

export class ExcelExporter {
    /**
     * @param {string} fileName
     * @param {int} row_count
     * @param {Array} columns
     * */
    constructor(fileName, row_count, columns) {
        if (fileName.endsWith('.xlsx') == false) {
            fileName += '.xlsx';
        }
        this.fileName = fileName;
        this.row_count = row_count;
        this.columns = columns;
        this.export = this.exportUseXlsxJs
        if (typeof showSaveFilePicker != "undefined" && row_count > 1000) {
            this.export = this.exportUseFilePicker
        }
    }

    async exportUseXlsxJs(rowExporter) {

        let xlsxModule = await importShim('./js/lib/xlsx-js-style.js')

        let XLSX = xlsxModule.default || xlsxModule

        let columns = this.columns;
        const datas = []

        const cellStyle = {
            border: {
                top: {style: 'thin'},
                bottom: {style: 'thin'},
                left: {style: 'thin'},
                right: {style: 'thin'}
            }
        }
        //添加title
        //  XLSX.utils.sheet_add_aoa(ws, [columns.map(it=>it.title)], { origin: "A1" });
        //设置title宽度和背景色
        let wsWidth = [];
        const headerRow = []
        for (let i = 0; i < columns.length; i++) {
            let column = columns[i]
            wsWidth[i] = {}
            if (column.width) {
                wsWidth[i].wpx = column.width
            }
            headerRow.push({
                v: column.title,
                t: 's',
                s: {
                    fill: {fgColor: {rgb: "DDEBF8"}},
                    border: cellStyle.border
                }
            })
        }
        datas.push(headerRow)
        const row_writer = async (row) => {
            // console.log(row)
            let rowArr = []
            for (let i = 0; i < columns.length; i++) {
                let c = columns[i]
                let d = row[c.prop]
                rowArr[i] = {
                    v: (d || "") + "",
                    t: 's',
                    s: cellStyle
                }
            }
            datas.push(rowArr)
        }
        await rowExporter(row_writer)
        const workbook = XLSX.utils.book_new();
        const ws = XLSX.utils.aoa_to_sheet(datas);
        ws["!cols"] = wsWidth
        XLSX.utils.book_append_sheet(workbook, ws, "Sheet1");
        XLSX.writeFile(workbook, this.fileName, {cellStyles: true});
        return this.fileName
    }

    /**
     *
     * @param {Function} rowExporter
     * */
    async exportUseFilePicker(rowExporter) {
        const newHandle = await showSaveFilePicker({
            suggestedName: this.fileName,
            types: [{
                desciption: "MicroSoft Excel 文件",
                accept: {
                    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx']
                }
            }]
        });
        let columns = this.columns;
        const writableStream = await newHandle.createWritable();
        const row_writer = async (row) => {
            let rowStrs = ['<Row>']
            for (let i = 0; i < columns.length; i++) {
                let c = columns[i]
                let d = row[c.prop]
                if (d == null) {
                    rowStrs.push(`<Cell ss:StyleID="s51"/>`)
                } else {
                    if (d instanceof String) {
                        d = escapeXml(d)
                    }
                    rowStrs.push(`<Cell ss:StyleID="s51"><Data ss:Type="String">${d}</Data></Cell>`)
                }
            }
            rowStrs.push('</Row>')

            await writableStream.write(rowStrs.join(''))
        }
        const now = new Date().toISOString()
        //wite header
        let h = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"><DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><Author>EasyBoot</Author><LastAuthor>EasyBoot</LastAuthor><Created>${now}</Created><LastSaved>${now}</LastSaved></DocumentProperties><CustomDocumentProperties xmlns="urn:schemas-microsoft-com:office:office"><ICV dt:dt="string">37B6C5C1F080C98AB5720B662EBB4B8D_41</ICV><KSOProductBuildVer dt:dt="string">2052-6.5.2.8766</KSOProductBuildVer></CustomDocumentProperties><ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><WindowWidth>25440</WindowWidth><WindowHeight>13120</WindowHeight><ProtectStructure>False</ProtectStructure><ProtectWindows>False</ProtectWindows></ExcelWorkbook><Styles><Style ss:ID="Default" ss:Name="Normal"><Alignment ss:Vertical="Center"/><Borders/><Font ss:FontName="宋体" x:CharSet="134" ss:Size="11" ss:Color="#000000"/><Interior/><NumberFormat/><Protection/></Style><Style ss:ID="s49"><Alignment ss:Vertical="Center"/><Borders/><Font ss:FontName="宋体" x:CharSet="134" ss:Size="11" ss:Color="#000000"/><Interior/><NumberFormat/><Protection/></Style><Style ss:ID="s50"><Alignment ss:Horizontal="Center" ss:Vertical="Center"/><Borders><Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/><Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/><Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/><Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/></Borders><Font ss:FontName="宋体" x:CharSet="134" ss:Size="11" ss:Color="#000000" ss:Bold="1"/><Interior ss:Color="#DDEBF7" ss:Pattern="Solid"/><NumberFormat/><Protection/></Style><Style ss:ID="s51"><Alignment ss:Vertical="Center"/><Borders><Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1"/><Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1"/><Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1"/><Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1"/></Borders><Font ss:FontName="宋体" x:CharSet="134" ss:Size="11" ss:Color="#000000"/><Interior/><NumberFormat/><Protection/></Style></Styles><Worksheet ss:Name="Sheet1"><Table ss:ExpandedColumnCount="${this.columns.length}" ss:ExpandedRowCount="${this.row_count}" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="51.2" ss:DefaultRowHeight="16.8">`
        writableStream.write(h);
        //witecolumns
        for (let i = 0; i < columns.length; i++) {
            let c = columns[i]
            let w = c.width
            if ((w instanceof String && w.endsWith('%')) || w == null) {
                w = 200
            }
            await writableStream.write(`<Column ss:Index="${i + 1}" ss:StyleID="s49" ss:AutoFitWidth="0" ss:Width="${w}"/>`)
        }
        await writableStream.write('<Column ss:StyleID="s49" ss:AutoFitWidth="0" ss:Width="71.65"/>')

        //wite header
        let header = ['<Row>']
        for (let i = 0; i < columns.length; i++) {
            let c = columns[i]
            header.push(`<Cell ss:StyleID="s50"><Data ss:Type="String">${c.title}</Data></Cell>`)
        }
        header.push('</Row>')
        await writableStream.write(header.join(''))
        //wite body
        await rowExporter(row_writer)
        //wite footer
        await writableStream.write('</Table><WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"><PageSetup><Header/><Footer/></PageSetup><Selected/><TopRowVisible>0</TopRowVisible><LeftColumnVisible>0</LeftColumnVisible><PageBreakZoom>100</PageBreakZoom><ProtectObjects>False</ProtectObjects><ProtectScenarios>False</ProtectScenarios></WorksheetOptions></Worksheet></Workbook>')
        //finish
        await writableStream.close();

        return newHandle.name;
    }
}

//
// function exportExcel(params={pageUrl:'',name:null,total:1000,columns:[]}){
//     params=Object.assign(params,{total:1000})
//
//     ajax.post(params.pageUrl,params)
// }
//html`xxx` 模板解析
// var a=function(p,f,c,n){var l;f[0]=0;for(var u=1;u<f.length;u++){var g=f[u++],o=f[u]?(f[0]|=g?1:2,c[f[u++]]):f[++u];g===3?n[0]=o:g===4?n[1]=Object.assign(n[1]||{},o):g===5?(n[1]=n[1]||{})[f[++u]]=o:g===6?n[1][f[++u]]+=o+"":g?(l=p.apply(o,a(p,o,c,["",null])),n.push(l),o[0]?f[0]|=2:(f[u-2]=0,f[u]=l)):n.push(o)}return n},M=new Map;function b(p){var f=M.get(this);return f||(f=new Map,M.set(this,f)),(f=a(this,f.get(p)||(f.set(p,f=function(c){for(var n,l,u=1,g="",o="",i=[0],s=function(v){u===1&&(v||(g=g.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?i.push(0,v,g):u===3&&(v||g)?(i.push(3,v,g),u=2):u===2&&g==="..."&&v?i.push(4,v,0):u===2&&g&&!v?i.push(5,0,!0,g):u>=5&&((g||!v&&u===5)&&(i.push(u,0,g,l),u=6),v&&(i.push(u,v,0,l),u=6)),g=""},t=0;t<c.length;t++){t&&(u===1&&s(),s(t));for(var w=0;w<c[t].length;w++)n=c[t][w],u===1?n==="<"?(s(),i=[i],u=3):g+=n:u===4?g==="--"&&n===">"?(u=1,g=""):g=n+g[0]:o?n===o?o="":g+=n:n==='"'||n==="'"?o=n:n===">"?(s(),u=1):u&&(n==="="?(u=5,l=g,g=""):n==="/"&&(u<5||c[t][w+1]===">")?(s(),u===3&&(i=i[0]),u=i,(i=i[0]).push(2,0,u),u=0):n===" "||n==="	"||n===`
// `||n==="\r"?(s(),u=2):g+=n),u===3&&g==="!--"&&(u=4,i=i[0])}return s(),i}(p)),f),arguments,[])).length>1?f:f[0]}
// export const html=a.bind(h)
/**
 *
 * @param promise Promise|Function|Object
 * @return {*}
 */
export const toRef = (promise) => {
    const value = ref()
    if (promise instanceof Promise) {
        promise.then(ret => value.value = ret.data ? ret.data : ret)
    } else if (promise instanceof Function) {
        let ret = promise(ret => value.value = ret)
        if (!ret) {
            value.value = ret
        }
    } else {
        value.value = promise;
    }

    return value;
}

function copy(sourceData) {
    if (sourceData !== Object(sourceData)) {
        return sourceData
    }
    if (sourceData instanceof Array) {
        return [...sourceData]
    }
    return {...sourceData}
}

export const useDlg = (sourceData, submitHandler = (data, isOpen) => {
}) => {

    const dlgData = reactive({
        isOpen: false,
        data: copy(sourceData)
    })
    dlgData.open = (_data) => {
        if (_data) {
            dlgData.data = _data
        } else {
            dlgData.data = copy(sourceData)
        }
        dlgData.isOpen = true
    }
    dlgData.close = () => {
        dlgData.data = null
        dlgData.isOpen = false
    }
    dlgData.submit = () => {
        let ret = submitHandler(dlgData.data)
        if (ret != null && ret instanceof Promise) {
            ret.then(_ => {
                dlgData.isOpen = false
            })
        } else {
            dlgData.isOpen = false
        }
        return ret
    }
    return dlgData
}
const _storeCaches = {}
/**
 *
 * @param name
 * @param initValue Object|Function
 */
export const useStore = (name, initValue = '') => {
    let store = _storeCaches[name]
    if (store == null) {
        store = ref()
        if (initValue != null && initValue instanceof Function) {
            let ret = initValue()
            if (ret instanceof Promise) {
                ret.then(r => store.value = r.data ? r.data : r);
            } else {
                store.value = ret
            }
        } else {
            store.value = initValue
        }
        _storeCaches[name] = store;
    }
    return store
}
const _eventCaches = {}
/**
 *
 * @param name
 * @return {{dispatchEvent(String, *): void, addEventListener(String, Function): void}}
 */
export const useEvent = (name = "default") => {
    let eventTarget = _eventCaches[name]
    if (eventTarget == null) {
        eventTarget = new EventTarget()
        _eventCaches[name] = eventTarget;
    }
    let listeners = []
    onUnmounted(_ => {
        for (let listener of listeners) {
            eventTarget.removeEventListener(listener[0], listener[1])
        }
        listeners = null
    })
    return {
        addEventListener(event, listener) {
            eventTarget.addEventListener(event, listener)
            listeners.push([event, listener])
        },
        dispatchEvent(event, payload) {
            let eventObj = new Event(event)
            eventObj.data = payload
            eventTarget.dispatchEvent(eventObj)
        }
    }
}
